package cn.topcode.unicorn.wxsdk;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import cn.topcode.unicorn.utils.StringUtil;
import cn.topcode.unicorn.wxsdk.base.WXMessageCenter;
import cn.topcode.unicorn.wxsdk.message.receive.event.ClickLinkMenuEvent;
import cn.topcode.unicorn.wxsdk.message.receive.event.ClickMessageMenuEvent;
import cn.topcode.unicorn.wxsdk.message.receive.event.Event;
import cn.topcode.unicorn.wxsdk.message.receive.event.LocationEvent;
import cn.topcode.unicorn.wxsdk.message.receive.event.ScanCodeWaitMsgEvent;
import cn.topcode.unicorn.wxsdk.message.receive.event.ScanQrcodeEvent;
import cn.topcode.unicorn.wxsdk.message.receive.event.SubscribeEvent;
import cn.topcode.unicorn.wxsdk.message.receive.msg.GenericMessage;
import cn.topcode.unicorn.wxsdk.message.receive.msg.ImageMessage;
import cn.topcode.unicorn.wxsdk.message.receive.msg.LinkMessage;
import cn.topcode.unicorn.wxsdk.message.receive.msg.LocationMessage;
import cn.topcode.unicorn.wxsdk.message.receive.msg.Message;
import cn.topcode.unicorn.wxsdk.message.receive.msg.ShortVideoMessage;
import cn.topcode.unicorn.wxsdk.message.receive.msg.TextMessage;
import cn.topcode.unicorn.wxsdk.message.receive.msg.VideoMessage;
import cn.topcode.unicorn.wxsdk.message.receive.msg.VoiceMessage;

/**
 * 微信API 配置Filter即可访问
 * 接收消息/事件以及组装和分发
 * @author Unicorn Lien
 * 2016年12月4日 下午6:54:36 创建
 */
public class WXFilter implements Filter {
    
    private static final String MEDIA_ID = "MediaId";

    private static final String EVENT_KEY = "EventKey";

    private String appId;
    
    private String appSecret;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        String code = request.getParameter("code");     //  TODO code的作用？
        this.process((HttpServletRequest) request, (HttpServletResponse) response);
    }

    @Override
    public void destroy() {
        
    }
    
    /**
     * 微信请求处理
     * @author Unicorn Lien
     * 2016年12月4日 上午10:29:59 创建
     * @param request
     * @param response
     */
    private void process(HttpServletRequest request, HttpServletResponse response) {
        try {
            OutputStream out = response.getOutputStream();

            //  验证微信服务器
            if("GET".equalsIgnoreCase(request.getMethod())) {
                String signature = request.getParameter("signature");
                String timestamp = request.getParameter("timestamp");
                String nonce = request.getParameter("nonce");
                String echostr = request.getParameter("echostr");
                if(StringUtil.isNotBlank(signature)
                        && StringUtil.isNotBlank(timestamp)
                        && StringUtil.isNotBlank(nonce)
                        && StringUtil.isNotBlank(echostr) ) {
                    out.write(echostr.getBytes());
                }
                return ;
            }

            //  解析xml消息
            SAXReader reader = new SAXReader();
            reader.setEncoding("utf-8");
            Document document = reader.read(request.getInputStream());
            document.setXMLEncoding("utf-8");
            Element root = document.getRootElement();
            //  获取消息类型
            Element msgTypeElement = root.element("MsgType");
            String msgType = msgTypeElement.getTextTrim();
            
            switch(msgType) {
            case GenericMessage.MSG_TYPE_TEXT:         //      文本消息
                handleTextMessage(root, msgType, out);
                break;
            case GenericMessage.MSG_TYPE_IMAGE:        //      图片消息
                handleImageMessage(root, msgType, out);
                break;
            case GenericMessage.MSG_TYPE_LINK:         //      链接消息
                handleLinkMessage(root, msgType, out);
                break;
            case GenericMessage.MSG_TYPE_LOCATION:         //      定位消息
                handleLocationMessage(root, msgType, out);
                break;
            case GenericMessage.MSG_TYPE_SHORT_VIDEO:      //      小视频消息
                handleShortVideoMessage(root, msgType, out);
                break;
            case GenericMessage.MSG_TYPE_VIDEO:        //      视频消息
                handleVideoMessage(root, msgType, out);
                break;
            case GenericMessage.MSG_TYPE_VOICE:        //      语音消息
                handleVoiceMessage(root, msgType,out);
                break;
            case GenericMessage.MSG_TYPE_EVENT:        //      事件
                handleEvent(root, msgType,out);
                break;
                default:
//                    logger.warn("无法识别的MsgType:" + msgType);
                    break;
            }
        } catch (Exception e) {
//            logger.warn("微信服务器的请求数据有误",e);
        }
    }
    
    /**
     * 处理事件
     * @author Unicorn Lien
     * 2016年12月4日 下午3:47:26 创建
     * @param root
     * @param msgType
     */
    private void handleEvent(Element root, String msgType, OutputStream out) {
        Element eventElement = root.element("Event");
        String event = eventElement.getTextTrim();
        switch(event) {
        case Event.EVENT_SUBSCRIBE:     //  订阅事件
        case Event.EVENT_UNSUBSCRIBE:   //  取消订阅
            handleSubscribeEvent(root,msgType,event,out);
            break;
        case Event.EVENT_CLICK:     //      点击拉取消息菜单事件
            handleClickMessageMenuEvent(root,msgType,event,out);
            break;
        case Event.EVENT_LOCATION:      //      上报地理位置事件
            handleLocationEvent(root,msgType,event,out);
            break;
        case Event.EVENT_SCAN:      //      扫描带参数二维码事件
            handleScanQrcodeEvent(root,msgType,event,out);
            break;
        case Event.EVENT_VIEW:      //      点击菜单链接跳转事件
            handleClickLinkMenuEvent(root,msgType,event,out);
            break;
        case Event.EVENT_SCANCODE_PUSH:     //  扫码推事件的事件推送
            handleScanCodePush(root, msgType, event, out);
            break;
        case Event.SCANCODE_WAITMSG:  //  扫码推事件且弹出“消息接收中”提示框的事件推送
            handleScanCodeWaitMsg(root, msgType, event, out);
            break;
        case Event.PIC_SYSPHOTO:  //  弹出系统拍照发图的事件推送
            handlePicSysPhoto(root, msgType, event, out);
            break;
        case Event.PIC_PHOTO_OR_ALBUM:  //  弹出拍照或者相册发图的事件推送
            handlePicPhotoOrAlbum(root, msgType, event, out);
            break;
        case Event.PIC_WEIXIN:  //  弹出微信相册发图器的事件推送
            handlePicWeixin(root, msgType, event, out);
            break;
        case Event.LOCATION_SELECT:  //  弹出地理位置选择器的事件推送
            break;
            default:
//                logger.warn("无法识别的Event:" + event);
                break;
        }
    }

    /**
     * TODO 弹出微信相册发图器的事件处理
     * @param root
     * @param msgType
     * @param event
     * @param out
     */
    private void handlePicWeixin(Element root, String msgType, String event,
            OutputStream out) {
        ClickLinkMenuEvent clickLinkMenuEvent = new ClickLinkMenuEvent();
        fillBasicProperty(root, clickLinkMenuEvent);
        clickLinkMenuEvent.setMsgType(msgType);
        clickLinkMenuEvent.setEvent(event);
        Element eventKeyElement = root.element(EVENT_KEY);
        String eventKey = eventKeyElement.getTextTrim();
        clickLinkMenuEvent.setEventKey(eventKey);
        broadcastClickLinkMenuEvent(clickLinkMenuEvent,out);
    }

    /**
     * TODO 弹出拍照或者相册发图的事件
     * @param root
     * @param msgType
     * @param event
     * @param out
     */
    private void handlePicPhotoOrAlbum(Element root, String msgType,
            String event, OutputStream out) {
        ClickLinkMenuEvent clickLinkMenuEvent = new ClickLinkMenuEvent();
        fillBasicProperty(root, clickLinkMenuEvent);
        clickLinkMenuEvent.setMsgType(msgType);
        clickLinkMenuEvent.setEvent(event);
        Element eventKeyElement = root.element(EVENT_KEY);
        String eventKey = eventKeyElement.getTextTrim();
        clickLinkMenuEvent.setEventKey(eventKey);
        broadcastClickLinkMenuEvent(clickLinkMenuEvent,out);
    }

    /**
     * TODO 弹出系统拍照发图的事件处理
     * @param root
     * @param msgType
     * @param event
     * @param out
     */
    private void handlePicSysPhoto(Element root, String msgType, String event,
            OutputStream out) {
        ClickLinkMenuEvent clickLinkMenuEvent = new ClickLinkMenuEvent();
        fillBasicProperty(root, clickLinkMenuEvent);
        clickLinkMenuEvent.setMsgType(msgType);
        clickLinkMenuEvent.setEvent(event);
        Element eventKeyElement = root.element(EVENT_KEY);
        String eventKey = eventKeyElement.getTextTrim();
        clickLinkMenuEvent.setEventKey(eventKey);
        broadcastClickLinkMenuEvent(clickLinkMenuEvent,out);
    }

    /**
     * 扫码推事件且弹出“消息接收中”提示框的事件组装及分发
     * @param root
     * @param msgType
     * @param event
     * @param out
     */
    private void handleScanCodeWaitMsg(Element root, String msgType,
            String event, OutputStream out) {
        ScanCodeWaitMsgEvent scanCodeWaitMsgEvent = new ScanCodeWaitMsgEvent();
        fillBasicProperty(root, scanCodeWaitMsgEvent);
        scanCodeWaitMsgEvent.setMsgType(msgType);
        scanCodeWaitMsgEvent.setEvent(event);
        Element eventKeyElement = root.element(EVENT_KEY);
        String eventKey = eventKeyElement.getTextTrim();
        scanCodeWaitMsgEvent.setEventKey(eventKey);
        Element scanCodeInfo = root.element("ScanCodeInfo");
        Element scanType = scanCodeInfo.element("ScanType");
        Element scanResult = scanCodeInfo.element("ScanResult");
        scanCodeWaitMsgEvent.setScanType(scanType.getTextTrim());
        scanCodeWaitMsgEvent.setScanResult(scanResult.getTextTrim());
        broadcastScanCodeWaitMsgEvent(scanCodeWaitMsgEvent,out);
    }

    private void broadcastScanCodeWaitMsgEvent(
            ScanCodeWaitMsgEvent scanCodeWaitMsgEvent, OutputStream out) {
        WXMessageCenter.getInstance().publish(scanCodeWaitMsgEvent, out);
    }

    /**
     * TODO 扫码推事件的事件组装及分发
     * @param root
     * @param msgType
     * @param event
     * @param out
     */
    private void handleScanCodePush(Element root, String msgType, String event,
            OutputStream out) {
        ClickLinkMenuEvent clickLinkMenuEvent = new ClickLinkMenuEvent();
        fillBasicProperty(root, clickLinkMenuEvent);
        clickLinkMenuEvent.setMsgType(msgType);
        clickLinkMenuEvent.setEvent(event);
        Element eventKeyElement = root.element(EVENT_KEY);
        String eventKey = eventKeyElement.getTextTrim();
        clickLinkMenuEvent.setEventKey(eventKey);
        broadcastClickLinkMenuEvent(clickLinkMenuEvent,out);
    }

    /**
     * 点击菜单链接跳转事件组装及分发
     * @param root
     * @param msgType
     * @param event
     * @param out
     */
    private void handleClickLinkMenuEvent(Element root, String msgType,
            String event, OutputStream out) {
        ClickLinkMenuEvent clickLinkMenuEvent = new ClickLinkMenuEvent();
        fillBasicProperty(root, clickLinkMenuEvent);
        clickLinkMenuEvent.setMsgType(msgType);
        clickLinkMenuEvent.setEvent(event);
        Element eventKeyElement = root.element(EVENT_KEY);
        String eventKey = eventKeyElement.getTextTrim();
        clickLinkMenuEvent.setEventKey(eventKey);
        broadcastClickLinkMenuEvent(clickLinkMenuEvent,out);
    }

    /**
     * 扫描带参数二维码事件组装及分发
     * @param root
     * @param msgType
     * @param event
     * @param out
     */
    private void handleScanQrcodeEvent(Element root, String msgType,
            String event, OutputStream out) {
        ScanQrcodeEvent scanQrcodeEvent = new ScanQrcodeEvent();
        fillBasicProperty(root, scanQrcodeEvent);
        scanQrcodeEvent.setMsgType(msgType);
        scanQrcodeEvent.setEvent(event);
        Element eventKeyElement = root.element(EVENT_KEY);
        String eventKey = eventKeyElement.getTextTrim();
        scanQrcodeEvent.setEventKey(eventKey);
        Element ticketElement = root.element("Ticket");
        String ticket = ticketElement.getTextTrim();
        scanQrcodeEvent.setTicket(ticket);
        WXMessageCenter.getInstance().publish(scanQrcodeEvent, out);
    }

    /**
     * 上报地理位置事件组装及分发
     * @param root
     * @param msgType
     * @param event
     * @param out
     */
    private void handleLocationEvent(Element root, String msgType, String event, OutputStream out) {
        LocationEvent locationEvent = new LocationEvent();
        fillBasicProperty(root, locationEvent);
        locationEvent.setMsgType(msgType);
        locationEvent.setEvent(event);
        Element latitudeElement = root.element("Latitude");
        double latitude = Double.parseDouble(latitudeElement.getTextTrim());
        locationEvent.setLatitude(latitude);
        Element longitudeElement = root.element("Longitude");
        double longitude = Double.parseDouble(longitudeElement.getTextTrim());
        locationEvent.setLongitude(longitude);
        Element precisionElement = root.element("Precision");
        double precision = Double.parseDouble(precisionElement.getTextTrim());
        locationEvent.setPrecision(precision);
        WXMessageCenter.getInstance().publish(locationEvent, out);
    }

    /**
     * 点击拉取消息菜单事件组装及分发
     * @param root
     * @param msgType
     * @param event
     * @param out
     */
    private void handleClickMessageMenuEvent(Element root, String msgType,
            String event, OutputStream out) {
        ClickMessageMenuEvent clickMessageMenuEvent = new ClickMessageMenuEvent();
        fillBasicProperty(root, clickMessageMenuEvent);
        clickMessageMenuEvent.setMsgType(msgType);
        clickMessageMenuEvent.setEvent(event);
        Element eventKeyElement = root.element(EVENT_KEY);
        String eventKey = eventKeyElement.getTextTrim();
        clickMessageMenuEvent.setEventKey(eventKey);
        WXMessageCenter.getInstance().publish(clickMessageMenuEvent, out);
    }

    /**
     * 订阅/取消订阅事件组装及分发
     * @param root
     * @param msgType
     * @param event
     * @param out
     */
    private void handleSubscribeEvent(Element root, String msgType, String event, OutputStream out) {
        SubscribeEvent subscribeEvent = new SubscribeEvent();
        fillBasicProperty(root, subscribeEvent);
        subscribeEvent.setMsgType(msgType);
        subscribeEvent.setEvent(event);
        Element eventKeyElement = root.element(EVENT_KEY);
        if(eventKeyElement != null) {
            String eventKey = eventKeyElement.getTextTrim();
            subscribeEvent.setEventKey(eventKey);
        }
        Element ticketElement = root.element("Ticket");
        if(ticketElement != null) {
            String ticket = ticketElement.getTextTrim();
            subscribeEvent.setTicket(ticket);
        }
        WXMessageCenter.getInstance().publish(subscribeEvent, out);
    }

    /**
     * 语音消息组装及分发
     * @param root
     * @param msgType
     * @param out
     */
    private void handleVoiceMessage(Element root, String msgType, OutputStream out) {
        VoiceMessage voiceMessage = new VoiceMessage();
        fillBasicProperty(root, voiceMessage);
        voiceMessage.setMsgType(msgType);
        Element formatElement = root.element("Format");
        String format = formatElement.getTextTrim();
        voiceMessage.setFormat(format);
        Element mediaIdElement = root.element(MEDIA_ID);
        String mediaId = mediaIdElement.getTextTrim();
        voiceMessage.setMediaId(mediaId);
        Element recognitionElement = root.element("Recognition");
        if(recognitionElement != null) {
            voiceMessage.setRecognition(recognitionElement.getTextTrim());
        }
        WXMessageCenter.getInstance().publish(voiceMessage, out);
    }

    /**
     * 视频消息组装及分发
     * @param root
     * @param msgType
     * @param out
     */
    private void handleVideoMessage(Element root, String msgType, OutputStream out) {
        VideoMessage videoMessage = new VideoMessage();
        fillBasicProperty(root, videoMessage);
        videoMessage.setMsgType(msgType);
        Element mediaIdElement = root.element(MEDIA_ID);
        String mediaId = mediaIdElement.getTextTrim();
        videoMessage.setMediaId(mediaId);
        Element thumbMediaIdElement = root.element("ThumbMediaId");
        String thumbMediaId = thumbMediaIdElement.getTextTrim();
        videoMessage.setThumbMediaId(thumbMediaId);
        WXMessageCenter.getInstance().publish(videoMessage, out);
    }

    /**
     * 小视频消息组装及分发
     * @param root
     * @param msgType
     * @param out
     */
    private void handleShortVideoMessage(Element root, String msgType, OutputStream out) {
        ShortVideoMessage shortVideoMessage = new ShortVideoMessage();
        fillBasicProperty(root, shortVideoMessage);
        shortVideoMessage.setMsgType(msgType);
        Element mediaIdElement = root.element(MEDIA_ID);
        String mediaId = mediaIdElement.getTextTrim();
        shortVideoMessage.setMediaId(mediaId);
        Element thumbMediaIdElement = root.element("ThumbMediaId");
        String thumbMediaId = thumbMediaIdElement.getTextTrim();
        shortVideoMessage.setThumbMediaId(thumbMediaId);
        WXMessageCenter.getInstance().publish(shortVideoMessage, out);
    }

    /**
     * 定位消息组装及分发
     * @param root
     * @param msgType
     * @param out
     */
    private void handleLocationMessage(Element root, String msgType, OutputStream out) {
        LocationMessage locationMessage = new LocationMessage();
        fillBasicProperty(root, locationMessage);
        locationMessage.setMsgType(msgType);
        Element labelElement = root.element("Label");
        String label = labelElement.getTextTrim();
        locationMessage.setLabel(label);
        Element locationXElement = root.element("Location_X");
        double locationX = Double.parseDouble(locationXElement.getTextTrim());
        locationMessage.setLocationX(locationX);
        Element locationYElement = root.element("Location_Y");
        double locationY = Double.parseDouble(locationYElement.getTextTrim());
        locationMessage.setLocationY(locationY);
        Element scaleElement = root.element("Scale");
        int scale = Integer.parseInt(scaleElement.getTextTrim());
        locationMessage.setScale(scale);
        WXMessageCenter.getInstance().publish(locationMessage, out);
    }

    /**
     * 链接消息组装及分发
     * @param root
     * @param msgType
     * @param out
     */
    private void handleLinkMessage(Element root, String msgType, OutputStream out) {
        LinkMessage linkMessage = new LinkMessage();
        fillBasicProperty(root, linkMessage);
        linkMessage.setMsgType(msgType);
        Element titleElement = root.element("Title");
        String title = titleElement.getTextTrim();
        linkMessage.setTitle(title);
        Element urlElement = root.element("Url");
        String url = urlElement.getTextTrim();
        linkMessage.setUrl(url);
        Element descriptionElement = root.element("Description");
        String description = descriptionElement.getTextTrim();
        linkMessage.setDescription(description);
        WXMessageCenter.getInstance().publish(linkMessage, out);
    }

    /**
     * 图片消息组装及分发
     * @param root
     * @param msgType
     * @param out
     */
    private void handleImageMessage(Element root, String msgType, OutputStream out) {
        ImageMessage imageMessage = new ImageMessage();
        fillBasicProperty(root, imageMessage);
        imageMessage.setMsgType(msgType);
        Element picUrlElement = root.element("PicUrl");
        String picUrl = picUrlElement.getTextTrim();
        imageMessage.setPicUrl(picUrl);
        Element mediaIdElement = root.element(MEDIA_ID);
        String mediaId = mediaIdElement.getTextTrim();
        imageMessage.setMediaId(mediaId);
        WXMessageCenter.getInstance().publish(imageMessage, out);
    }

    /**
     * 文本消息组装及分发
     * @param root
     * @param msgType
     * @param out
     */
    private void handleTextMessage(Element root, String msgType, OutputStream out) {
        TextMessage textMessage = new TextMessage();
        fillBasicProperty(root, textMessage);
        textMessage.setMsgType(msgType);
        Element contentElement = root.element("Content");
        String content = contentElement.getTextTrim();
        textMessage.setContent(content);
        WXMessageCenter.getInstance().publish(textMessage, out);
    }

    /*发出事件及消息的广播*/
    protected void broadcastClickLinkMenuEvent(ClickLinkMenuEvent clickLinkMenuEvent, OutputStream out) {
        WXMessageCenter.getInstance().publish(clickLinkMenuEvent, out);
    }

    //  无论是消息还是事件都具有ToUserName、FromUserName、CreateTime、msgType四个属性，
    // 此方法封装填充消息基本属性，供其他消息或事件组装时调用
    //  TODO 此方法中未填充msgType属性
    private void fillBasicProperty(Element root, Message message) {
        Element toUserNameElement = root.element("ToUserName");
        String toUserName = toUserNameElement.getTextTrim();
        Element fromUserNameElement = root.element("FromUserName");
        String fromUserName = fromUserNameElement.getTextTrim();
        Element createTimeElement = root.element("CreateTime");
        Date createTime = new Date(Long.parseLong(createTimeElement.getTextTrim()));
        
        message.setCreateTime(createTime);
        message.setFromUserName(fromUserName);
        message.setToUserName(toUserName);

        //  如果是消息而非事件，将具备MsgId属性
        if(message instanceof GenericMessage) {
            Element msgIdElement = root.element("MsgId");
            long msgId = Long.parseLong(msgIdElement.getTextTrim());
            ((GenericMessage)message).setMsgId(msgId);
        }
    }

    public void setAppId(String appId) {
        this.appId = appId;
        WXContext.setAppId(this.appId);
    }

    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
        WXContext.setAppSecret(this.appSecret);
    }

}
